<!--
    @Title: LeaferRevoke
    @Author  dudalei
    @Package 
    @Date  2024/10/28 15:18
    @description: 实现撤销功能
-->
<script setup lang="ts">
import { DragEvent, KeyEvent, App, type IKeyEvent } from 'leafer-ui'
import { produce, enablePatches, applyPatches, type Patch } from 'immer'
import { InnerEditorEvent } from 'leafer-editor'

import { cloneDeep } from 'lodash-es'

type Queue = { [key: number]: { redo: Patch[]; undo: Patch[] } }

function isPlainValue(data: any) {
  return !(Object.prototype.toString.call(data) === '[object Object]' || Array.isArray(data))
}

let app: App
let current = -1 // 前进后退的索引值
let maxQueueValue = 50 // 最大存放数
let queue: Queue = {} //  存放所有的操作命令
let initialState: { [key: string]: any } = {}

function listen() {
  // 文本内部编辑器关闭事件/文本的添加也会触发该事件
  app.editor.on(InnerEditorEvent.CLOSE, change)
  // 直接监听ChildEvent.ADD不合适，比如文字的添加/线段的绘制，都是当前绘制完成后才发布add事件
  app.tree.on('add', change)
  // set(json)会触发add事件，所以需要自己在需要的地方发布remove事件
  app.tree.on('reomve', change)
  // 直接监听PropertyEvent.CHANGE的话图形的拖拽/旋转等操作会多次触发，因此也是自己在合适的时机去手动发布update事件
  app.tree.on('update', change)
  // DragEvent.END目前还是合适的，在change事件中有比较补丁是否存在，所以如果点击后没有操作，也不用担心会被收集操作
  app.editor.on(DragEvent.END, change)
  // 监听键盘事件
  app.on(KeyEvent.DOWN, onKeydown)
}
function destroy() {
  app.editor.off(InnerEditorEvent.CLOSE, change)
  app.tree.off('add', change)
  app.tree.off('reomve', change)
  app.tree.off('update', change)
  app.editor.off(DragEvent.END, change)
  app.off(KeyEvent.DOWN, onKeydown)
}
function onKeydown(event: IKeyEvent) {
  if (
    event.origin?.target instanceof HTMLInputElement ||
    event.origin?.target instanceof HTMLTextAreaElement
  ) {
    // event.stop && event.stop()
    // event.stopDefault && event.stopDefault()
    return
  }
  const { key, shiftKey, altKey, ctrlKey, metaKey } = event
  const keyString: string[] = []
  if (ctrlKey || metaKey) keyString.push('ctrl')
  if (shiftKey) keyString.push('shift')
  if (altKey) keyString.push('alt')
  const keyCode = key?.toLocaleLowerCase()
  if (keyCode) {
    if (!keyString.includes(keyCode)) {
      keyString.push(keyCode)
    }
  }
  const keyName = keyString.join('+')
  if (keyName === 'ctrl+z') {
    undo()
  } else if (keyName === 'ctrl+shift+z') {
    redo()
  }
}
function change() {
  const json = cloneDeep(app.tree.toJSON())
  const data: { [key: string]: any } = {}
  json.children?.forEach((child: any) => (data[child.id] = child))
  initialState = produce(
    initialState,
    (draft) => {
      const deepCompareAndMerge = (draft: any, data: any) => {
        const mergeData = { ...draft, ...data }
        // TODO 比较老数据和新数据的变化，这里只做了一层比较，后续做改造
        Object.keys(mergeData).forEach((key) => {
          // 新的有，老得没有，直接赋值
          if (!draft[key]) {
            draft[key] = data[key]
          } else if (!data[key]) {
            // 新的没有，老得有，直接删除
            delete draft[key]
          } else {
            if (isPlainValue(draft[key]) && isPlainValue(data[key])) {
              draft[key] = data[key]
            } else {
              // 两个都有，并且都不是普通值
              deepCompareAndMerge(draft[key], data[key])
            }
          }
        })
      }
      deepCompareAndMerge(draft, data)
    },
    (patches, inversePatches) => {
      // 没有任何变化
      if (patches.length === 0 && inversePatches.length === 0) return
      current++
      queue[current] = {
        redo: patches,
        undo: inversePatches,
      }
      // 把后一个操作删除，不能在反撤销了，cmd不存在直接return
      delete queue[current + 1]
      // 超出最大队列的删除
      delete queue[current - maxQueueValue]
      // console.log('patches', patches);
      // console.log('inversePatches', inversePatches);
      // console.log('queue', queue);
    }
  )
  // console.log('nextState', initialState);
}

function redo() {
  const cmd = queue[current + 1] // 找到当前的下一步还原操作
  if (cmd) {
    initialState = produce(initialState, (draft) => {
      applyPatches(draft, cmd.redo)
    })
    const data = {
      hitChildren: true,
      tag: 'Leafer',
      children: cloneDeep(Object.values(initialState)),
    }
    const listId = app.editor.list.map((item) => item.id!)
    const hoverId = app.editor.hoverTarget?.id
    app.tree.set(data)
    current++
    if (listId.length > 0) {
      const list = listId.map((id: string) => app.tree.findId(id)).filter((v) => !!v)
      app.editor.select(list)
    }
    if (hoverId) {
      app.editor.hoverTarget = app.tree.findId(hoverId)
    }
  }
}
function undo() {
  const cmd = queue[current] // 找到上一步还原
  if (cmd) {
    initialState = produce(initialState, (draft) => {
      applyPatches(draft, cmd.undo)
    })
    const data = {
      hitChildren: true,
      tag: 'Leafer',
      children: cloneDeep(Object.values(initialState)),
    }
    const listId = app.editor.list.map((item) => item.id!)
    const hoverId = app.editor.hoverTarget?.id
    app.tree.set(data)
    current--
    if (listId.length > 0) {
      const list = listId.map((id: string) => app.tree.findId(id)).filter((v) => !!v)
      app.editor.select(list)
    }
    if (hoverId) {
      app.editor.hoverTarget = app.tree.findId(hoverId)
    }
  }
}

function init() {
  enablePatches()
  listen()
}

// 导入画布对象
const leaferApp = inject(LeaferInjectKey)
watchEffect(() => {
  if (leaferApp && leaferApp.value) {
    app = leaferApp.value
    init()
  }
})

onBeforeUnmount(destroy)
</script>

<template>
  <div></div>
</template>

<style scoped lang="scss"></style>
